package com.gitee.wsl.doc.prosemirror.model.dom

import com.fleeksoft.ksoup.nodes.Element
import com.gitee.wsl.doc.prosemirror.model.content.ContentMatch
import com.gitee.wsl.doc.prosemirror.model.mark.MarkType
import com.gitee.wsl.doc.prosemirror.model.Whitespace
import com.gitee.wsl.doc.prosemirror.model.node.NodeType
import com.fleeksoft.ksoup.nodes.Node as DOMNode

// TODO move all regex patterns here to avoid parsing multiple times
object RegexPatterns {
    // /[^=]*/
    val STYLE_PROP_REGEX = "[^=]*".toRegex()
}

enum class PreserveWhitespace {
    YES,
    NO,
    FULL
}




val blockTags = setOf(
    "address", "article", "aside", "blockquote", "canvas", "dd", "div", "dl", "fieldset", "figcaption", "figure",
    "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li", "noscript", "ol", "output",
    "p", "pre", "section", "table", "tfoot", "ul"
)

val ignoreTags = setOf("head", "noscript", "object", "script", "style", "title")

val listTags = setOf("ol", "ul")

// Using a bitfield for node context options
const val OPT_PRESERVE_WS = 1
const val OPT_PRESERVE_WS_FULL = 2
const val OPT_OPEN_LEFT = 4

fun wsOptionsFor(type: NodeType?, preserveWhitespace: PreserveWhitespace?, base: Int): Int {
    if (preserveWhitespace != null) {
        return (if (preserveWhitespace != PreserveWhitespace.NO) OPT_PRESERVE_WS else 0) or
            (if (preserveWhitespace == PreserveWhitespace.FULL) OPT_PRESERVE_WS_FULL else 0)
    }
    return if (type?.whitespace == Whitespace.PRE) {
        OPT_PRESERVE_WS or OPT_PRESERVE_WS_FULL
    } else {
        base and (OPT_OPEN_LEFT.inv())
    }
}

// Kludge to work around directly nested list nodes produced by some
// tools and allowed by browsers to mean that the nested list is
// actually part of the list item above it.
fun normalizeList(dom: DOMNode) {
    var child = dom.firstChild()
    var prevItem: DOMNode? = null
    while (child != null) {
        val name = if (child is Element) child.nodeName().lowercase() else null
        if (name != null && listTags.contains(name) && prevItem != null) {
            (prevItem as Element).appendChild(child)
            child = prevItem
        } else if (name == "li") {
            prevItem = child
        } else if (name != null) {
            prevItem = null
        }
        child = child.nextSibling()
    }
}

// Apply a CSS selector.
fun matches(dom: DOMNode, selector: String): Boolean {
    return (dom as? Element)?.`is`(selector) ?: false
//    return htmlDomElementMatches(dom, selector)
}

// fun copy(obj: {[prop: string]: any}) {
//     val copy: {[prop: string]: any} = {}
//     for (val prop in obj) copy[prop] = obj[prop]
//     return copy
// }

// Used when finding a mark at the top level of a fragment parse.
// Checks whether it would be reasonable to apply a given mark type to
// a given node, by looking at the way the mark occurs in the schema.
fun markMayApply(markType: MarkType, nodeType: NodeType): Boolean {
    val nodes = nodeType.schema.nodes
    for (name in nodes.keys) {
        val parent = nodes[name]
        if (parent?.allowsMarkType(markType) != true) continue
        val seen = mutableListOf<ContentMatch>()
        if (scan(nodeType, seen, parent.contentMatch)) return true
    }
    return false
}

@Suppress("ReturnCount")
private fun scan(nodeType: NodeType, seen: MutableList<ContentMatch>, match: ContentMatch): Boolean {
    seen.add(match)
    for (i in 0 until match.edgeCount) {
        val (type, next) = match.edge(i)
        if (type == nodeType) return true
        if (seen.indexOf(next) < 0 && scan(nodeType, seen, next)) return true
    }
    return false
}

fun Element.styles(): Map<String, String>? {
    val style = attribute("style")?.value ?: return null
    return style.split(";")
        .map { if (it.contains(":")) it else "$it:" }
        .associate { item ->
            item.split(":", limit = 2).let { it[0].trim() to it[1].trim() }
        }
}
